package com.example.demoweb.framework.mybatis.common.interceptor;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Reflections
{
  private static final String SETTER_PREFIX = "set";
  private static final String GETTER_PREFIX = "get";
  private static final String CGLIB_CLASS_SEPARATOR = "$$";
  private static Logger logger = LoggerFactory.getLogger(Reflections.class);

  public static Object invokeGetter(Object obj, String propertyName)
  {
    Object object = obj;
    for (String name : StringUtils.split(propertyName, ".")) {
      String getterMethodName = "get" + StringUtils.capitalize(name);
      object = invokeMethod(object, getterMethodName, new Class[0], new Object[0]);
    }
    return object;
  }

  public static void invokeSetter(Object obj, String propertyName, Object value)
  {
    Object object = obj;
    String[] names = StringUtils.split(propertyName, ".");
    for (int i = 0; i < names.length; i++)
      if (i < names.length - 1) {
        String getterMethodName = "get" + StringUtils.capitalize(names[i]);
        object = invokeMethod(object, getterMethodName, new Class[0], new Object[0]);
      } else {
        String setterMethodName = "set" + StringUtils.capitalize(names[i]);
        invokeMethodByName(object, setterMethodName, new Object[] { value });
      }
  }

  public static Object getFieldValue(Object obj, String fieldName)
  {
    Field field = getAccessibleField(obj, fieldName);

    if (field == null) {
      throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
    }

    Object result = null;
    try {
      result = field.get(obj);
    } catch (IllegalAccessException e) {
      logger.error("不可能抛出的异常{}", e.getMessage());
    }
    return result;
  }

  public static void setFieldValue(Object obj, String fieldName, Object value)
  {
    Field field = getAccessibleField(obj, fieldName);

    if (field == null) {
      throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
    }
    try
    {
      field.set(obj, value);
    } catch (IllegalAccessException e) {
      logger.error("不可能抛出的异常:{}", e.getMessage());
    }
  }

  public static Object invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args)
  {
    Method method = getAccessibleMethod(obj, methodName, parameterTypes);
    if (method == null) {
      throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
    }
    try
    {
      return method.invoke(obj, args); 
      } catch (Exception e) {
    	  return convertReflectionExceptionToUnchecked(e);
    }
  }

  public static Object invokeMethodByName(Object obj, String methodName, Object[] args)
  {
    Method method = getAccessibleMethodByName(obj, methodName);
    if (method == null) {
      throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
    }
    try
    {
      return method.invoke(obj, args); 
      } catch (Exception e) {
    	  return convertReflectionExceptionToUnchecked(e);
    }
  }

  public static Field getAccessibleField(Object obj, String fieldName)
  {
    Validate.notNull(obj, "object can't be null", new Object[0]);
    Validate.notBlank(fieldName, "fieldName can't be blank", new Object[0]);
    for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
      try {
        Field field = superClass.getDeclaredField(fieldName);
        makeAccessible(field);
        return field;
      }
      catch (NoSuchFieldException e)
      {
      }
    }
    return null;
  }

  public static Method getAccessibleMethod(Object obj, String methodName, Class<?>[] parameterTypes)
  {
    Validate.notNull(obj, "object can't be null", new Object[0]);
    Validate.notBlank(methodName, "methodName can't be blank", new Object[0]);

    for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
      try {
        Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
        makeAccessible(method);
        return method;
      }
      catch (NoSuchMethodException e)
      {
      }
    }
    return null;
  }

  public static Method getAccessibleMethodByName(Object obj, String methodName)
  {
    Validate.notNull(obj, "object can't be null", new Object[0]);
    Validate.notBlank(methodName, "methodName can't be blank", new Object[0]);

    for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
      Method[] methods = searchType.getDeclaredMethods();
      for (Method method : methods) {
        if (method.getName().equals(methodName)) {
          makeAccessible(method);
          return method;
        }
      }
    }
    return null;
  }

  public static void makeAccessible(Method method)
  {
    if (((!Modifier.isPublic(method.getModifiers())) || (!Modifier.isPublic(method.getDeclaringClass().getModifiers()))) && 
      (!method
      .isAccessible()))
      method.setAccessible(true);
  }

  public static void makeAccessible(Field field)
  {
    if (((!Modifier.isPublic(field.getModifiers())) || (!Modifier.isPublic(field.getDeclaringClass().getModifiers())) || 
      (Modifier.isFinal(field
      .getModifiers()))) && (!field.isAccessible()))
      field.setAccessible(true);
  }

  public static <T> Class<T> getClassGenricType(Class clazz)
  {
    return getClassGenricType(clazz, 0);
  }

  public static Class getClassGenricType(Class clazz, int index)
  {
    Type genType = clazz.getGenericSuperclass();

    if (!(genType instanceof ParameterizedType)) {
      logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
      return Object.class;
    }

    Type[] params = ((ParameterizedType)genType).getActualTypeArguments();

    if ((index >= params.length) || (index < 0)) {
      logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + params.length);

      return Object.class;
    }
    if (!(params[index] instanceof Class)) {
      logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
      return Object.class;
    }

    return (Class)params[index];
  }

  public static Class<?> getUserClass(Object instance) {
    Validate.notNull(instance, "Instance must not be null", new Object[0]);
    Class clazz = instance.getClass();
    if ((clazz != null) && (clazz.getName().contains("$$"))) {
      Class superClass = clazz.getSuperclass();
      if ((superClass != null) && (!Object.class.equals(superClass))) {
        return superClass;
      }
    }
    return clazz;
  }

  public static RuntimeException convertReflectionExceptionToUnchecked(Exception e)
  {
    if (((e instanceof IllegalAccessException)) || ((e instanceof IllegalArgumentException)) || ((e instanceof NoSuchMethodException)))
    {
      return new IllegalArgumentException(e);
    }if ((e instanceof InvocationTargetException))
      return new RuntimeException(((InvocationTargetException)e).getTargetException());
    if ((e instanceof RuntimeException)) {
      return (RuntimeException)e;
    }
    return new RuntimeException("Unexpected Checked Exception.", e);
  }

  public static Field getFieldByFieldName(Object obj, String fieldName)
  {
    if ((obj == null) || (fieldName == null)) {
      return null;
    }
    for (Class superClass = obj.getClass(); superClass != Object.class; )
    {
      try {
        return superClass.getDeclaredField(fieldName);
      }
      catch (Exception localException)
      {
      }
      superClass = superClass
        .getSuperclass();
    }

    return null;
  }

  public static Object getValueByFieldName(Object obj, String fieldName)
  {
    Object value = null;
    try {
      Field field = getFieldByFieldName(obj, fieldName);
      if (field != null)
        if (field.isAccessible()) {
          value = field.get(obj);
        } else {
          field.setAccessible(true);
          value = field.get(obj);
          field.setAccessible(false);
        }
    }
    catch (Exception localException) {
    }
    return value;
  }

  public static <T> Object getValueByFieldType(Object obj, Class<T> fieldType)
  {
    Object value = null;
    for (Class superClass = obj.getClass(); superClass != Object.class; )
    {
      try {
        Field[] fields = superClass.getDeclaredFields();
        for (Field f : fields) {
          if (f.getType() == fieldType) {
            if (f.isAccessible()) {
              value = f.get(obj);
              break;
            }
            f.setAccessible(true);
            value = f.get(obj);
            f.setAccessible(false);
            break;
          }
        }

        if (value != null)
          break;
      }
      catch (Exception localException)
      {
      }
      superClass = superClass
        .getSuperclass();
    }

    return value;
  }

  public static boolean setValueByFieldName(Object obj, String fieldName, Object value)
  {
    try
    {
      Field field = obj.getClass().getDeclaredField(fieldName);

      if (field.isAccessible()) {
        field.set(obj, value);
      } else {
        field.setAccessible(true);
        field.set(obj, value);
        field.setAccessible(false);
      }
      return true;
    } catch (Exception localException) {
    }
    return false;
  }
}